package com.yc.cinema.aop;

import com.yc.cinema.MyApplicationContext;
import com.yc.cinema.bean.User;
import com.yc.cinema.biz.BizException;
import com.yc.cinema.biz.UserBiz;
import com.yc.cinema.web.UserAction;
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//继承于自定义的 IOC 容器
public class AopApplicationContext extends MyApplicationContext {

    Map<String, AspectInfo> aspectMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws InstantiationException, IllegalAccessException, InvocationTargetException, UnsupportedEncodingException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
        super(configClass);

        //查找组件中的切面类 => @Aspect 注解
        List<Object> aspects = new ArrayList<>();
        final Map<String, MyApplicationContext.BeanInfo> ioc = getIoc();
        for (BeanInfo beanInfo : ioc.values()) {
            final Object bean = beanInfo.getBean();
            final Aspect aspect = bean.getClass().getAnnotation(Aspect.class);
            if (aspect != null) {
                aspects.add(bean);
            }
        }
        if (aspects.isEmpty()) {
            return;
        }

        //统计切点与增强 Pointcut + Advice, 封装切面信息对象, 保存切面集合中
        for (Object aspect : aspects) {
            final Method[] methods = aspect.getClass().getDeclaredMethods();
            for (Method method : methods) {
                final Pointcut pointcut = method.getAnnotation(Pointcut.class);
                final Before before = method.getAnnotation(Before.class);
                final After after = method.getAnnotation(After.class);
//                final After afterReturning = method.getAnnotation(afterReturning.class);
//                final After afterThrowing = method.getAnnotation(afterThrowing.class);
                // 其它增强注解, 请自行扩展
                if (pointcut != null) {
                    // 切点方法
                    System.out.println("切点方法: " + pointcut.value());
                } else if (before != null) {
                    // 前置增强方法
                    addAdvice(before.value(), aspect, method, "befores");
                } else if (after != null) {
                    // 后置增强方法
                    addAdvice(after.value(), aspect, method, "afters");
                }
            }
        }

        //扫描所有的组件, 查看是否包含被切点拦截的方法
        List<BeanInfo> aopBeans = new ArrayList<>();
        a:for (BeanInfo beanInfo : ioc.values()) {
            final Object bean = beanInfo.getBean();
            for (Method method : bean.getClass().getDeclaredMethods()) {
                if(checkAspect(method) != null){
                    // 如果匹配成功, 说明当前bean 包含 切点拦截的方法
                    aopBeans.add(beanInfo);
                    continue a;
                }
            }
        }
        aopBeans.forEach(System.out::println);
        //如果组件包含被切点拦截的方法, 那么生成动态代理对象替换原有的组件
        for (BeanInfo beanInfo : aopBeans) {
            //beanInfo.getBean();
            Enhancer enhancer = new Enhancer();
            // 設置父類
            enhancer.setSuperclass(beanInfo.getBean().getClass());
            // 设置回调
            enhancer.setCallback(new MethodInterceptor() {
                // 保存原有的被代理对象
                Object oldBean = beanInfo.getBean();
                @Override
                public Object intercept(
                        Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    //判断当前方法是否被拦截
                    final AspectInfo aspectInfo = checkAspect(method);
                    if(aspectInfo == null ){
                        // 未切到
                        Object res = methodProxy.invoke(oldBean, args);
                        return res;
                    } else {
                        // 被切到
                        try{
                            for (Method before : aspectInfo.getBefores()) {
                                // 执行前置增强
                                before.invoke(aspectInfo.aspect, new JoinPoint(){

                                    @Override
                                    public String toShortString() {
                                        return before.getName();
                                    }

                                    @Override
                                    public String toLongString() {
                                        return before.toString();
                                    }

                                    @Override
                                    public Object getThis() {
                                        return null;
                                    }

                                    @Override
                                    public Object getTarget() {
                                        return null;
                                    }

                                    @Override
                                    public Object[] getArgs() {
                                        return new Object[0];
                                    }

                                    @Override
                                    public Signature getSignature() {
                                        return null;
                                    }

                                    @Override
                                    public SourceLocation getSourceLocation() {
                                        return null;
                                    }

                                    @Override
                                    public String getKind() {
                                        return null;
                                    }

                                    @Override
                                    public StaticPart getStaticPart() {
                                        return null;
                                    }
                                });
                            }
                            Object res = methodProxy.invoke(oldBean, args);
                            for (Method m : aspectInfo.getAfterReturnings()) {
                                // 执行返回增强
                                m.invoke(aspectInfo.aspect, new JoinPoint(){

                                    @Override
                                    public String toShortString() {
                                        return m.getName();
                                    }

                                    @Override
                                    public String toLongString() {
                                        return m.toString();
                                    }

                                    @Override
                                    public Object getThis() {
                                        return null;
                                    }

                                    @Override
                                    public Object getTarget() {
                                        return null;
                                    }

                                    @Override
                                    public Object[] getArgs() {
                                        return new Object[0];
                                    }

                                    @Override
                                    public Signature getSignature() {
                                        return null;
                                    }

                                    @Override
                                    public SourceLocation getSourceLocation() {
                                        return null;
                                    }

                                    @Override
                                    public String getKind() {
                                        return null;
                                    }

                                    @Override
                                    public StaticPart getStaticPart() {
                                        return null;
                                    }
                                });
                            }
                            return res;
                        } catch (Throwable t){
                            t.printStackTrace();
                            for (Method m : aspectInfo.getAfterThrowings()) {
                                // 执行异常增强
                                m.invoke(aspectInfo.aspect, new JoinPoint(){

                                    @Override
                                    public String toShortString() {
                                        return m.getName();
                                    }

                                    @Override
                                    public String toLongString() {
                                        return m.toString();
                                    }

                                    @Override
                                    public Object getThis() {
                                        return null;
                                    }

                                    @Override
                                    public Object getTarget() {
                                        return null;
                                    }

                                    @Override
                                    public Object[] getArgs() {
                                        return new Object[0];
                                    }

                                    @Override
                                    public Signature getSignature() {
                                        return null;
                                    }

                                    @Override
                                    public SourceLocation getSourceLocation() {
                                        return null;
                                    }

                                    @Override
                                    public String getKind() {
                                        return null;
                                    }

                                    @Override
                                    public StaticPart getStaticPart() {
                                        return null;
                                    }
                                });
                            }
                        } finally {
                            for (Method m : aspectInfo.getAfters()) {
                                // 执行后置增强
                                m.invoke(aspectInfo.aspect, new JoinPoint(){

                                    @Override
                                    public String toShortString() {
                                        return m.getName();
                                    }

                                    @Override
                                    public String toLongString() {
                                        return m.toString();
                                    }

                                    @Override
                                    public Object getThis() {
                                        return null;
                                    }

                                    @Override
                                    public Object getTarget() {
                                        return null;
                                    }

                                    @Override
                                    public Object[] getArgs() {
                                        return new Object[0];
                                    }

                                    @Override
                                    public Signature getSignature() {
                                        return null;
                                    }

                                    @Override
                                    public SourceLocation getSourceLocation() {
                                        return null;
                                    }

                                    @Override
                                    public String getKind() {
                                        return null;
                                    }

                                    @Override
                                    public StaticPart getStaticPart() {
                                        return null;
                                    }
                                });
                            }
                        }
                    }
                    return null;
                }
            });
            // 生成代理对象
            final Object proxyBean = enhancer.create();
            // 用代理对象替换被代理对象
            beanInfo.setBean(proxyBean);
        }

    }

    /**
     * 判断当前方法是否被切到, 如果被切到, 则返回该切面封装对象
     * @param method
     * @return
     */
    AspectInfo checkAspect(Method method){
        for (Map.Entry<String,AspectInfo> entry : aspectMap.entrySet()) {
            String pointcutExp = entry.getKey();
            String methodExp = pointcutExp.replaceAll("execution\\((.+)\\)", "$1");
            // 第一个 *  public User
            methodExp = methodExp.replaceFirst("\\*", "\\\\S+\\\\s+\\\\S+");
            // 包路径中的单词 *.*
            methodExp = methodExp.replace("*", "\\w+");
            // 参数 (..)
            methodExp = methodExp.replace("(..)", "\\(.*\\)");
            // 包路径中的 ..
            methodExp = methodExp.replace("..", "\\.\\w+(\\.\\w+)*\\.");

            //System.out.println("method.toString() = " + method.toString());
            //System.out.println("methodExp = " + methodExp);
            if(method.toString().matches(methodExp)){
                return entry.getValue();
            }
        }
        return null;
    }

    public void addAdvice(String pointcutExp, Object aspect, Method method, String adviceName)
            throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
//        System.out.println("前置增强方法:"+before.value());
//        String pointcutExp = before.value();
        if (pointcutExp.startsWith("execution") ||
                pointcutExp.startsWith("within") /*....*/) {
            //  pointcutExp
        } else if (pointcutExp.matches("\\w+\\(\\s*\\)")) {
            // 引用切点表达式  例如: pointcut()
            // 切点方法名 : pointcut
            String pointcutMethodName = pointcutExp.replaceAll("(\\w+)\\(\\s*\\)", "$1");
            final Method declaredMethod = aspect.getClass().getDeclaredMethod(pointcutMethodName);
            final Pointcut pointcut1 = declaredMethod.getAnnotation(Pointcut.class);
            pointcutExp = pointcut1.value();
        }

        AspectInfo aspectInfo = aspectMap.get(pointcutExp);
        if (aspectInfo == null) {
            aspectInfo = new AspectInfo(aspect, pointcutExp);
            aspectMap.put(pointcutExp, aspectInfo);
        }
        // 添加前置拦截方法
        final Field field = aspectInfo.getClass().getDeclaredField(adviceName);
        field.setAccessible(true);
        List methods = (List) field.get(aspectInfo);
        methods.add(method);

    }

    public static void main(String[] args) throws UnsupportedEncodingException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, BizException, InterruptedException {
        AopApplicationContext ctx;
        ctx = new AopApplicationContext(AopConfig.class);
        ctx.aspectMap.forEach((k,v)->{
            System.out.println(k);
            System.out.println(v);
        });
        System.out.println("--------------------------------------------");
        final UserBiz userBiz = ctx.getBean(UserBiz.class);
        //userBiz.register(null);
        userBiz.login("root","123");
        final UserAction userAction = ctx.getBean(UserAction.class);
        System.out.println("--------------------------------------------");
        final List<User> users = userAction.queryAll();

    }

    // 切点封装对象
    @Data
    class AspectInfo {
        Object aspect;
        String pointcut;
        List<Method> befores = new ArrayList<>();
        List<Method> afterReturnings = new ArrayList<>();
        List<Method> afterThrowings = new ArrayList<>();
        List<Method> afters = new ArrayList<>();
        List<Method> arounds = new ArrayList<>();

        public AspectInfo(Object aspect, String pointcut) {
            this.aspect = aspect;
            this.pointcut = pointcut;
        }
    }

}
